#ifndef BATCHED_KERNEL_FUNCTOR_H
#define BATCHED_KERNEL_FUNCTOR_H

#include "CL/cl.hpp"

namespace RayTracePro
{
	class MultipleWaitHandle
	{
	private:
		static const int StaticBufferSize = 16;
		cl::Event events[StaticBufferSize];
		cl::Event *overflow;
		int count, ptr;
		void InitOverflowBuffer()
		{
			if (count > StaticBufferSize)
				overflow = new cl::Event[count - StaticBufferSize];
			else
				overflow = 0;
		}
		void Free()
		{
			if (overflow)
				delete [] overflow;
		}
	public:
		MultipleWaitHandle(int _count)
		{
			count = _count;
			ptr = 0;
			InitOverflowBuffer();

		}
		MultipleWaitHandle(const MultipleWaitHandle & other)
			: count(0), overflow(0), ptr(0)
		{
			*this = other;
		}
		MultipleWaitHandle(MultipleWaitHandle && other)
			: overflow(0), count(0), ptr(0)
		{
			*this = std::move(other);
		}
		MultipleWaitHandle & operator = (MultipleWaitHandle && other)
		{
			Free();
			overflow = other.overflow;
			count = other.count;
			ptr = other.ptr;
			for (int i = 0; i<StaticBufferSize; i++)
				events[i] = other.events[i];
			other.count = 0;
			other.overflow = 0;
			other.ptr = 0;
			return *this;
		}
		MultipleWaitHandle & operator = (const MultipleWaitHandle & other)
		{
			Free();
			for (int i = 0; i<StaticBufferSize; i++)
				events[i] = other.events[i];
			count = other.count;
			ptr = other.ptr;
			InitOverflowBuffer();
			for (int i = 0; i < count - StaticBufferSize; i++)
				overflow[i] = other.overflow[i];
			return *this;
		}
		~MultipleWaitHandle()
		{
			Free();
		}
		void Wait()
		{
			clWaitForEvents(min(StaticBufferSize, count), (cl_event*)events);
			if (count > StaticBufferSize)
			{
				clWaitForEvents(count-StaticBufferSize, (cl_event*)overflow);
			}
		}
		void Add(cl::Event evt)
		{
			if (ptr < StaticBufferSize)
			{
				events[ptr++] = evt;
			}
			else
			{
#ifdef _DEBUG
				if (ptr >= count)
					throw "Index out of range";
#endif
				overflow[(ptr++) - StaticBufferSize] = evt;
			}
		}
	};

	class BatchedKernelFunctor
	{
	private:
		cl::Kernel kernel;
		cl::CommandQueue queue;
		int globalSize;
		int groupSize;
		int batchSize;
	public:
		BatchedKernelFunctor(cl::CommandQueue &_queue, cl::Kernel & _kernel, int _globalSize, int _groupSize = 256, int _batchSize = 0)
		{
			queue = _queue;
			kernel = _kernel;
			globalSize = _globalSize;
			groupSize = _groupSize;
			batchSize = _batchSize;
		}

		MultipleWaitHandle operator ()()
		{
			int batches = (globalSize + batchSize - 1)/batchSize;
			MultipleWaitHandle result(batches);
			int offset = 0;
			for (int i = 0; i<batches; i++)
			{
				result.Add(kernel.bind(queue, cl::NDRange(min(batchSize, globalSize - offset)), cl::NDRange(groupSize))
					(offset));
				offset += batchSize;
			}
			return result;
		}

		#define KERNEL_CALL_TYPE_LIST typename T1
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1
		#define KERNEL_CALL_ARGUMENT_LIST arg1
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST
		
		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6, const T7 & arg7
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6, arg7
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6, const T7 & arg7, const T8 & arg8
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6, const T7 & arg7, const T8 & arg8, const T9 & arg9
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6, const T7 & arg7, const T8 & arg8, const T9 & arg9, const T10 & arg10
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10, typename T11
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6, const T7 & arg7, const T8 & arg8, const T9 & arg9, const T10 & arg10, const T11 & arg11
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10, typename T11, typename T12
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6, const T7 & arg7, const T8 & arg8, const T9 & arg9, const T10 & arg10, const T11 & arg11, const T12 & arg12
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10, typename T11, typename T12, typename T13
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6, const T7 & arg7, const T8 & arg8, const T9 & arg9, const T10 & arg10, const T11 & arg11, const T12 & arg12, const T13 & arg13
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10, typename T11, typename T12, typename T13, typename T14
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6, const T7 & arg7, const T8 & arg8, const T9 & arg9, const T10 & arg10, const T11 & arg11, const T12 & arg12, const T13 & arg13, const T14 & arg14
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST

		#define KERNEL_CALL_TYPE_LIST typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10, typename T11, typename T12, typename T13, typename T14, typename T15
		#define KERNEL_CALL_PARAMETER_LIST const T1 & arg1, const T2 & arg2, const T3 & arg3, const T4 & arg4, const T5 & arg5, const T6 & arg6, const T7 & arg7, const T8 & arg8, const T9 & arg9, const T10 & arg10, const T11 & arg11, const T12 & arg12, const T13 & arg13, const T14 & arg14, const T15 & arg15
		#define KERNEL_CALL_ARGUMENT_LIST arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15
		#include "KernelCall.h"
		#undef KERNEL_CALL_TYPE_LIST
		#undef KERNEL_CALL_PARAMETER_LIST
		#undef KERNEL_CALL_ARGUMENT_LIST
	};
}

#endif